using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using Gibbed.IO;
using Gibbed.MassEffect3.FileFormats;
using Gibbed.MassEffect3.FileFormats.SFXArchive;
using SevenZip.Compression.LZMA;

using System.ComponentModel;
using System.Windows.Forms;

namespace AmaroK86.MassEffect3.DLC
{
    using FileTable = SortedDictionary<FileNameHash, string>;
    public class FileStatus
    {
        public action Key;
        public string Value;
        public FileStatus(action key, string value) { Key = key; Value = value; }
    }

    public enum action
    {
        copy,
        add,
        replace,
        delete
    };

    public class sfarCollection : SortedKeyedCollection<FileNameHash, sfarFile>
    {
        public sfarCollection() : base() { }
        protected override FileNameHash GetKeyForItem(sfarFile item)
        {
            return item.nameHash;
        }

        // this indexing allows easier access to data editing
        public sfarFile this[string searchName]
        {
            get
            {
                return this.First(x => x.fileName == searchName);
            }
            /*set
            {
                this[FileNameHash.Compute(searchName)] = value;
            }*/
        }

    }

    public class sfarFile : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public string fileName;
        public FileNameHash nameHash;
        public long entryOffset;
        public int blockSizeIndex;
        public ushort[] blockSizeArray;
        public long uncompressedSize;
        public long compressedSize { get { if (blockSizeIndex == -1) return uncompressedSize; else return blockSizeArray.Sum(a => (long)a); } }
        public long[] dataOffset;
        private FileStatus _fileStatus;
        public FileStatus fileStatus
        {
            get { return _fileStatus; }
            set
            {
                if (_fileStatus != value)
                {
                    _fileStatus = value;
                    // Call OnPropertyChanged whenever the property is updated
                    OnPropertyChanged("fileStatus");
                }
            }
        }

        public sfarFile() { }

        public sfarFile(string newFileName)
        {
            fileName = newFileName;
            nameHash = FileNameHash.Compute(fileName);
        }

        // Create the OnPropertyChanged method to raise the event 
        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }
    }

    #region DLCHandler

    public class DLCHandler
    {
        public string sfarFileName { get; private set; }

        public sfarCollection fileList = new sfarCollection();
        private static readonly FileNameHash fileListHash = new FileNameHash(new byte[] { 0xB5, 0x50, 0x19, 0xCB, 0xF9, 0xD3, 0xDA, 0x65, 0xD5, 0x5B, 0x32, 0x1C, 0x00, 0x19, 0x69, 0x7C, });

        private static uint magic = 0x53464152;
        public Endian endian     { get; private set; }
        public uint version      { get; private set; }
        public uint maxBlockSize { get; private set; }
        public uint numOfFiles   { get; private set; }
        public long filesUncSize { get { return fileList.Where(entry => entry.fileStatus.Key == action.copy).Sum(entry => entry.uncompressedSize); } }
        public long filesComprSize
        {
            get
            {
                long uncFiles = fileList.Where(entry => entry.fileStatus.Key == action.copy).Where(entry => entry.blockSizeIndex == -1).Sum(entry => entry.uncompressedSize);
                long cprFiles = fileList.Where(entry => entry.fileStatus.Key == action.copy).Where(entry => entry.blockSizeIndex != -1).Sum(entry => entry.blockSizeArray.Sum(a => (long)a));
                return uncFiles + cprFiles;
            }
        }
        public CompressionScheme CompressionScheme { get; private set; }
        public uint entryOffset      { get; private set; }
        public uint blockTableOffset { get; private set; }
        public uint dataOffset       { get; private set; }
        private int originalFileCount;

        public bool bUnchanged { get { return fileList.Where(entry => entry.fileStatus.Key != action.copy).Count() == 0; } }

        public DLCHandler(string sfarFileName)
        {
            // if file not exists then create a new DLC
            if (!File.Exists(sfarFileName))
            {
                version = 0x00010000;
                maxBlockSize = 0x00010000;
                numOfFiles = 0x0;
                entryOffset = 0x00000020;
                blockTableOffset = 0x00000020;
                dataOffset = 0x00000020;
                //default values are for PC, setting Little Endianess and LZMA compression
                endian = Endian.Little;
                CompressionScheme = CompressionScheme.Lzma;
                fileList = new sfarCollection();
                originalFileCount = 1;
                return;
            }

            this.sfarFileName = Path.GetFullPath(sfarFileName);
            
            var fileStream = File.OpenRead(sfarFileName);
            getStructure(fileStream);

            var fileListEntry = fileList[fileListHash];

            if (fileListEntry == null)
                throw new FormatException("File list not found.");

            using (var outStream = new MemoryStream())
            {
                decompressEntry(fileListHash, fileStream, outStream);

                outStream.Position = 0;

                var reader = new StreamReader(outStream);
                while (reader.EndOfStream == false)
                {
                    string line = reader.ReadLine();
                    fileList[FileNameHash.Compute(line)].fileName = line;
                }
            }
            dataOffset = (uint)fileList.Min(entry => entry.dataOffset[0]);
            originalFileCount = fileList.Count;
            fileStream.Close();
        }

        private void getStructure(Stream input)
        {
            var fileMagic = input.ReadValueU32(Endian.Little);
            if (fileMagic != magic && // SFAR
                fileMagic.Swap() != magic)
            {
                throw new FormatException("Not a valid sfar file.");
            }
            var endian = fileMagic == magic ? Endian.Little : Endian.Big;

            var version = input.ReadValueU32(endian);
            if (version != 0x00010000)
            {
                throw new FormatException("Not supported version.");
            }

            dataOffset = input.ReadValueU32(endian);
            uint minDataOffset = dataOffset;
            entryOffset = input.ReadValueU32(endian);
            var fileTableCount = numOfFiles = input.ReadValueU32(endian);
            blockTableOffset = input.ReadValueU32(endian);
            maxBlockSize = input.ReadValueU32(endian);
            this.CompressionScheme = input.ReadValueEnum<CompressionScheme>(endian);

            if (entryOffset != 0x20)
            {
                throw new FormatException();
            }

            if (maxBlockSize != 0x010000)
            {
                throw new FormatException();
            }

            if (this.CompressionScheme != CompressionScheme.None &&
                this.CompressionScheme != CompressionScheme.Lzma &&
                this.CompressionScheme != CompressionScheme.Lzx)
            {
                throw new FormatException();
            }

            input.Seek(entryOffset, SeekOrigin.Begin);
            for (uint i = 0; i < fileTableCount; i++)
            {
                sfarFile entry = new sfarFile();
                entry.fileStatus = new FileStatus(action.copy, "");
                entry.entryOffset = input.Position;
                entry.nameHash = input.ReadFileNameHash();
                entry.blockSizeIndex = input.ReadValueS32(endian);
                entry.uncompressedSize = input.ReadValueU32(endian);
                entry.uncompressedSize |= ((long)input.ReadValueU8()) << 32;
                //totalUncSize += entry.uncompressedSize;

                if (entry.blockSizeIndex == -1)
                {
                    entry.dataOffset = new long[1];
                    entry.dataOffset[0] = input.ReadValueU32(endian);
                    entry.dataOffset[0] |= ((long)input.ReadValueU8()) << 32;
                }
                else
                {
                    int numBlocks = (int)Math.Ceiling((double)entry.uncompressedSize / (double)maxBlockSize);
                    entry.dataOffset = new long[numBlocks];
                    entry.blockSizeArray = new ushort[numBlocks];
                    entry.dataOffset[0] = input.ReadValueU32(endian);
                    entry.dataOffset[0] |= ((long)input.ReadValueU8()) << 32;

                    long lastPosition = input.Position;
                    input.Seek(getBlockOffset(entry.blockSizeIndex), 0);
                    entry.blockSizeArray[0] = input.ReadValueU16();

                    for (int j = 1; j < numBlocks; j++)
                    {
                        entry.blockSizeArray[j] = input.ReadValueU16();
                        entry.dataOffset[j] = entry.dataOffset[j - 1] + entry.blockSizeArray[j];
                    }
                    input.Seek(lastPosition, 0);
                }
                fileList.Add(entry);
            }// end of foreach
        }

        private long getBlockOffset(int blockIndex)
        {
            return (long)(entryOffset + (numOfFiles * 0x1E) + (blockIndex * 2));
        }

        private long Getsize(string filename)
        {
            FileInfo fInfo = new FileInfo(filename);
            return fInfo.Length;
        }

        private void decompressEntry(FileNameHash hash, Stream input, Stream output)
        {
            byte[] inputBlock;
            byte[] outputBlock = new byte[maxBlockSize];

            var entry = fileList[hash];
            input.Seek(entry.dataOffset[0], SeekOrigin.Begin);

            if (entry.blockSizeIndex == -1)
            {
                output.WriteFromStream(input, entry.uncompressedSize);
            }
            else
            {
                var left = entry.uncompressedSize;
                int count = 0;
                while (left > 0)
                {
                    uint compressedBlockSize = (uint)entry.blockSizeArray[count];
                    if (compressedBlockSize == 0)
                    {
                        compressedBlockSize = maxBlockSize;
                    }

                    if (CompressionScheme == CompressionScheme.None)
                    {
                        output.WriteFromStream(input, compressedBlockSize);
                        left -= compressedBlockSize;
                    }
                    else if (CompressionScheme == CompressionScheme.Lzma)
                    {
                        if (compressedBlockSize == maxBlockSize ||
                            compressedBlockSize == left)
                        {
                            output.WriteFromStream(input, compressedBlockSize);
                            left -= compressedBlockSize;
                        }
                        else
                        {
                            var uncompressedBlockSize = (uint)Math.Min(left, maxBlockSize);

                            if (compressedBlockSize < 5)
                            {
                                throw new InvalidDataException();
                            }

                            inputBlock = new byte[compressedBlockSize];

                            //var properties = input.ReadBytes(5);
                            //compressedBlockSize -= 5;

                            if (input.Read(inputBlock, 0, (int)compressedBlockSize)
                                != compressedBlockSize)
                            {
                                throw new EndOfStreamException();
                            }

                            uint actualUncompressedBlockSize = uncompressedBlockSize;
                            uint actualCompressedBlockSize = compressedBlockSize;

                            /*var error = LZMA.Decompress(
                                outputBlock,
                                ref actualUncompressedBlockSize,
                                inputBlock,
                                ref actualCompressedBlockSize,
                                properties,
                                (uint)properties.Length);

                            if (error != LZMA.ErrorCode.Ok ||
                                uncompressedBlockSize != actualUncompressedBlockSize ||
                                compressedBlockSize != actualCompressedBlockSize)
                            {
                                throw new InvalidOperationException();
                            }*/

                            outputBlock = SevenZipHelper.Decompress(inputBlock, (int)actualUncompressedBlockSize);
                            if (outputBlock.Length != actualUncompressedBlockSize)
                                throw new NotImplementedException();

                            output.Write(outputBlock, 0, (int)actualUncompressedBlockSize);
                            left -= uncompressedBlockSize;
                        }
                    }
                    else
                    {
                        throw new NotImplementedException();
                    }
                    count++;
                }
            }
        }// end of DecompressEntry

        private void CompressFile(Stream inStream, out ushort[] blockSizes, MemoryStream outStream)
        {
            if (inStream == null)
                throw new NullReferenceException("Error: null input stream");
            if (!inStream.CanRead)
                throw new IOException("Error: not an input readable stream");
            if (outStream == null)
                throw new NullReferenceException("Error: null output stream");
            if (!outStream.CanWrite)
                throw new IOException("Error: not an output writable stream");

            int fileLength = (int)inStream.Length;
            blockSizes = new ushort[(int)Math.Ceiling((double)fileLength / (double)maxBlockSize)];

            byte[] inputBlock;
            byte[] outputBlock = new byte[maxBlockSize + 100];

            uint templength = (uint)fileLength;
            int blockCounter = 0;
            uint actualUncompressedBlockSize;
            uint actualCompressedBlockSize;

            while (templength > 0)
            {
                if (templength > maxBlockSize)
                {
                    actualUncompressedBlockSize = maxBlockSize;
                    actualCompressedBlockSize = maxBlockSize;
                }
                else
                {
                    actualUncompressedBlockSize = templength;
                    actualCompressedBlockSize = templength;
                }
                inputBlock = new byte[actualUncompressedBlockSize];
                inStream.Read(inputBlock, 0, (int)actualUncompressedBlockSize);

                outputBlock = SevenZipHelper.Compress(inputBlock);
                actualCompressedBlockSize = (uint)outputBlock.Length;

                /*var error = LZMA.Compress(
                                inputBlock,
                                ref actualCompressedBlockSize,
                                outputBlock,
                                actualUncompressedBlockSize,
                                properties,
                                ref prLength);

                //Console.WriteLine("  properties: {0}",String.Concat(Array.ConvertAll(properties, x => x.ToString("X2"))) );
                if (error != LZMA.ErrorCode.Ok)
                {
                    Console.WriteLine("error: {0}",error);
                    throw new InvalidOperationException();
                }*/

                uint maxCompressedBlockSize = (uint)outputBlock.Length;
                //maxCompressedBlockSize += prLength;

                if (maxCompressedBlockSize >= actualUncompressedBlockSize ||
                    maxCompressedBlockSize >= maxBlockSize)
                {
                    outStream.Write(inputBlock, 0, (int)actualUncompressedBlockSize);
                    if (templength < maxBlockSize)
                    {
                        ushort mini = (ushort)actualUncompressedBlockSize;
                        maxCompressedBlockSize = mini;
                    }
                    else
                    {
                        maxCompressedBlockSize = 0;
                    }
                }
                else
                {
                    //outStream.Write(properties, 0, properties.Length);
                    outStream.Write(outputBlock, 0, (int)actualCompressedBlockSize);
                }
                ushort finalCompressedBlockSize = (ushort)maxCompressedBlockSize;
                blockSizes[blockCounter++] = finalCompressedBlockSize;

                //updating the pointer to the last position of Block Size
                if (templength > maxBlockSize)
                {
                    templength -= maxBlockSize;
                }
                else
                {
                    templength = 0;
                }
            }// end of while
        }//end of function

        public void extractFile(string dlcFileName, string outputFileName)
        {
            var dlcFileHash = FileNameHash.Compute(dlcFileName);
            if (!fileList.Contains(dlcFileHash))
                throw new FileNotFoundException(dlcFileName + " not found inside DLC.");

            using (FileStream dlcStream = File.OpenRead(sfarFileName), outFileStream = File.Create(outputFileName))
            {
                decompressEntry(dlcFileHash, dlcStream, outFileStream);
            }
        }

        public void extractFolder(string dlcFolder, string outputFolder)
        {
            List<sfarFile> filesInFolder = fileList.
                Where(entry => entry.fileStatus.Key == action.copy).
                Where(entry => entry.fileName != null && entry.fileName.Substring(0, dlcFolder.Length).CompareTo(dlcFolder) == 0).
                ToList();

            //opening sfar file stream
            using (FileStream dlcStream = File.OpenRead(sfarFileName))
            {
                foreach (sfarFile entry in filesInFolder)
                {
                    int substrpos = 0;
                    if (dlcFolder != "/")
                    {
                        string dlcFolderName = Path.GetFileName(Path.GetDirectoryName(dlcFolder));
                        substrpos = dlcFolder.Length - dlcFolderName.Length - 2;
                    }

                    string outputFileName = outputFolder + entry.fileName.Substring(substrpos).Replace('/', '\\');

                    //creating full folder structure
                    if (!Directory.Exists(Path.GetDirectoryName(outputFileName)))
                        Directory.CreateDirectory(Path.GetDirectoryName(outputFileName));

                    using (FileStream outFileStream = File.Create(outputFileName))
                    {
                        decompressEntry(entry.nameHash, dlcStream, outFileStream);
                    }
                }
            }
        }

        public action addFile(string selectedFile, string dlcFolder)
        {
            if (dlcFolder[dlcFolder.Length-1] != '/')
                dlcFolder += "/";
            string fullFileName = Path.GetFullPath(selectedFile);
            string dlcFileName = dlcFolder + Path.GetFileName(selectedFile);

            // check if file is already present in the dlc
            if (fileList.Contains(FileNameHash.Compute(dlcFileName)))
            {
                replaceFile(dlcFileName, selectedFile);
                return action.replace;
            }

            sfarFile entryToAdd = new sfarFile(dlcFolder + Path.GetFileName(selectedFile));
            entryToAdd.fileStatus = new FileStatus(action.add, fullFileName);

            fileList.Add(entryToAdd);
            return action.add;
        }

        public void addFolder(string selectedFolder, string dlcFolder)
        {

        }

        public void deleteFile(string selectedDLCFile)
        {
            FileNameHash dlcFileHash = FileNameHash.Compute(selectedDLCFile);
            deleteFile(dlcFileHash);
        }

        public void deleteFile(FileNameHash dlcFileHash)
        {
            switch (fileList[dlcFileHash].fileStatus.Key)
            {
                case action.add: fileList.Remove(dlcFileHash);
                    break;
                case action.copy:
                case action.replace:
                case action.delete:
                    fileList[dlcFileHash].fileStatus = new FileStatus(action.delete, "");
                    break;
            }
        }

        public void deleteFolder(string selectedDLCFolder)
        {
            List<sfarFile> fileListToDelete = fileList.Where(entry => entry.fileName != null && entry.fileName.Substring(0, selectedDLCFolder.Length).CompareTo(selectedDLCFolder) == 0).ToList();

            foreach (sfarFile entry in fileListToDelete)
            {
                deleteFile(entry.nameHash);
            }
        }

        public void replaceFile(string dlcFileName, string selectedFile)
        {
            FileNameHash dlcFileHash = FileNameHash.Compute(dlcFileName);

            switch (fileList[dlcFileHash].fileStatus.Key)
            {
                case action.add:
                    fileList.Remove(dlcFileHash);
                    addFile(selectedFile, Path.GetDirectoryName(dlcFileName));
                    break;
                case action.copy:
                case action.replace:
                case action.delete:
                    fileList[dlcFileHash].fileStatus = new FileStatus(action.replace, selectedFile);
                    break;
            }
        }

        public void renameFile(string selectedDLCFile, string newDLCFileName)
        {
            FileNameHash dlcFileHash = FileNameHash.Compute(selectedDLCFile);
            
            sfarFile renamedEntry = fileList[dlcFileHash];
            fileList.Remove(dlcFileHash);
            renamedEntry.fileName = newDLCFileName;
            renamedEntry.nameHash = FileNameHash.Compute(newDLCFileName);
            fileList.Add(renamedEntry);
        }

        public void renameFolder(string selectedDLCFolder)
        {

        }

        public void undo(string selectedDLCFile)
        {
            FileNameHash dlcFileHash = FileNameHash.Compute(selectedDLCFile);
            undo(dlcFileHash);
        }

        public void undo(FileNameHash dlcFileHash)
        {
            switch (fileList[dlcFileHash].fileStatus.Key)
            {
                case action.add: fileList.Remove(dlcFileHash);
                    break;
                case action.copy:
                case action.replace:
                case action.delete:
                    fileList[dlcFileHash].fileStatus = new FileStatus(action.copy, "");
                    break;
            }
        }

        public void commitChanges(WindowProgressForm winProg, object args)
        {
            commitChanges(sfarFileName, false, winProg);
        }

        public void commitChanges(string newSfarFile, bool makeBackup = false, WindowProgressForm winProg = null)
        {
            //string newSfarFile = sfarFileName;

            int count = 0;
            var inputPath = sfarFileName;
            if (!File.Exists(inputPath))
                throw new FileNotFoundException("Error: the input file doesn't exists");

            newSfarFile = Path.GetFullPath(newSfarFile);
            if (sfarFileName == newSfarFile)
                newSfarFile += ".bak";

            int inPointerBlockSize = 0;
            int outPointerEntry = 0x20;
            int outPointerEntryFileList = 0;
            int outPointerBlockSize = 0;
            int outPointerData = 0;

            int blocksToRemove = 0;
            int blocksToAdd = 0;

            using (FileStream input = File.OpenRead(inputPath),
                   output = File.Create(newSfarFile))
            {
                winProg.Invoke((Action)delegate
                {
                    winProg.Text = "Saving sfar file";
                    winProg.lblCommand.Text = "Compressing files";
                    winProg.progressBar.Maximum = fileList.Where(x => x.fileStatus.Key != action.delete).Count();
                    winProg.progressBar.Value = 0;
                });

                string dlcFileList = "";
                int outNumOfEntries = 1;
                //recreating the file list
                foreach (sfarFile entry in fileList)
                {
                    if (entry.nameHash == DLCHandler.fileListHash)
                        continue;
                    if (entry.fileStatus.Key != action.delete)
                    {
                        outNumOfEntries++;
                        dlcFileList += entry.fileName + Environment.NewLine;
                    }
                }

                // calculating new space for DLC's internal file list
                blocksToRemove += fileList[DLCHandler.fileListHash].blockSizeArray.Length;
                blocksToAdd += (int)Math.Ceiling((double)dlcFileList.Length / (double)maxBlockSize);

                foreach (var entry in fileList.Where(x => x.fileStatus.Key == action.add).ToList())
                {
                    string fPath = entry.fileStatus.Value;
                    if ((Path.GetExtension(fPath) != ".bik" &&
                         Path.GetExtension(fPath) != ".afc"))
                        blocksToAdd += (int)Math.Ceiling((double)Getsize(fPath) / (double)maxBlockSize);
                }

                foreach (var entry in fileList.Where(x => x.fileStatus.Key == action.replace).ToList())
                {
                    string fPath = entry.fileStatus.Value;
                    if (entry.blockSizeIndex != -1)
                    {
                        blocksToRemove += entry.blockSizeArray.Length;
                        blocksToAdd += (int)Math.Ceiling((double)Getsize(fPath) / (double)maxBlockSize);
                    }
                }

                foreach (var entry in fileList.Where(x => x.fileStatus.Key == action.delete).ToList())
                {
                    if (entry.blockSizeIndex != -1)
                        blocksToRemove += entry.blockSizeArray.Length;
                }

                var inputBlock = new byte[maxBlockSize];
                var outputBlock = new byte[maxBlockSize];
                //writing header of new sfar file
                input.Seek(0, 0);
                input.Read(inputBlock, 0, 32);
                output.Write(inputBlock, 0, 32);

                //getting initial blocks and data offsets
                //int newFileListCount = fileList.Where(entry => entry.fileStatus.Key != action.delete).Count();
                inPointerBlockSize = 0x20 + (originalFileCount * 0x1E);
                outPointerBlockSize = 0x20 + ((int)outNumOfEntries * 0x1E);
                int inDataOffset = (int)dataOffset;
                int outBlockCount = ((inDataOffset - inPointerBlockSize) / 2) - blocksToRemove + blocksToAdd;
                outPointerData = outPointerBlockSize + (outBlockCount * 2);

                //MessageBox.Show("blocks to add: " + blocksToAdd + "\nblocks to remove: " + blocksToRemove + "\nnew data offset: 0x" + outPointerData.ToString("X8") + "\nnum of entries: " + outNumOfEntries);

                //writing new header's values
                output.Seek(8, 0);
                output.WriteValueS32(outPointerData);

                output.Seek(16, 0);
                output.WriteValueS32(outNumOfEntries);

                output.Seek(20, 0);
                output.WriteValueS32(outPointerBlockSize);

                int numBlocks;
                int outDataOffset;
                int blockIndexCounter = 0;
                int outBlockIndex = 0;
                int fileSize = 0;

                int outInitialDataOffset = outPointerData;
                int outInitialBlockOffset = outPointerBlockSize;

                foreach (sfarFile entry in fileList)
                {
                    if (winProg.backgroundWorker.CancellationPending)
                    {
                        return;
                    }

                    if (entry.fileStatus.Key == action.delete)
                        continue;

                    winProg.Invoke((Action)delegate
                    {
                        winProg.progressBar.Value = ++count;
                        winProg.richTextBox.Text = count + "\\" + winProg.progressBar.Maximum + " - " + Path.GetFileName(entry.fileName);
                    });

                    if (entry.nameHash == DLCHandler.fileListHash)
                    {
                        //Console.WriteLine("File List Found at {0:X8}", outPointerEntry);
                        outPointerEntryFileList = outPointerEntry;
                        outPointerEntry += 0x1E;
                        continue;
                    }

                    if (entry.fileStatus.Key == action.add)// || entry.fileStatus.Key == action.replace)
                    {
                        string fPath = entry.fileStatus.Value;
                        entry.dataOffset = new long[1];
                        fileSize = (int)Getsize(fPath);
                        if ((Path.GetExtension(fPath) == ".bik" ||
                             Path.GetExtension(fPath) == ".afc"))
                            entry.blockSizeIndex = -1;
                    }
                    else
                    {
                        fileSize = (int)entry.uncompressedSize;
                    }

                    outDataOffset = outPointerData;
                    outBlockIndex = blockIndexCounter;

                    /*#if (WITH_GUI)
                                        int perc = (int)Math.Ceiling((float)count++ / (float)listComplete.Count * 100);
                                        if (perc > highPerc)
                                        {
                                            highPerc = perc;
                                            if (perc > 100)
                                                perc = 100;
                                            worker.ReportProgress(perc);
                                        }
                    #endif*/

                    switch (entry.fileStatus.Key)
                    {
                        case action.copy:
                            //worker.ReportProgress(0, count + "/" + listComplete.Count + ": Copying " + Path.GetFileName(entry.fileName));
                            if (entry.blockSizeIndex == -1)
                            {
                                inDataOffset = (int)entry.dataOffset[0];
                                input.Seek((long)inDataOffset, 0);
                                inputBlock = new byte[fileSize];
                                input.Read(inputBlock, 0, fileSize);

                                output.Seek((long)outPointerData, 0);
                                output.Write(inputBlock, 0, fileSize);
                                outPointerData += fileSize;

                                outBlockIndex = entry.blockSizeIndex;
                            }
                            else
                            {
                                numBlocks = (int)Math.Ceiling((double)fileSize / (double)maxBlockSize);
                                inDataOffset = (int)entry.dataOffset[0];
                                for (int i = 0; i < numBlocks; i++)
                                {
                                    uint blockSize = entry.blockSizeArray[i];
                                    if ((ushort)blockSize != entry.blockSizeArray[i])
                                        throw new Exception("different blocksizes");
                                    blockSize = blockSize == 0 ? maxBlockSize : blockSize;

                                    inputBlock = new byte[blockSize];
                                    input.Seek((long)inDataOffset, 0);
                                    input.Read(inputBlock, 0, (int)blockSize);
                                    inDataOffset += (int)blockSize;

                                    output.Seek((long)outPointerBlockSize, 0);
                                    if (blockSize == maxBlockSize)
                                        output.WriteValueU16(0);
                                    else
                                        output.WriteValueU16((ushort)blockSize);
                                    if (outPointerBlockSize > outInitialDataOffset)
                                        throw new Exception("Block index offset values out of range,\n  last block: " + blockIndexCounter + "\n  Pointer Block: " + outPointerBlockSize.ToString("X8") + "\n  Data Offset: " + outInitialDataOffset.ToString("X8"));
                                    outPointerBlockSize += 2;

                                    output.Seek((long)outPointerData, 0);
                                    output.Write(inputBlock, 0, (int)blockSize);
                                    if (output.Position - outPointerData != blockSize)
                                    {
                                        Console.WriteLine("  diff position: {0}, blocksize: {1}", output.Position - outPointerData, blockSize);
                                        throw new Exception("error writing file");
                                    }
                                    outPointerData += (int)blockSize;
                                }
                                blockIndexCounter += numBlocks;
                            }
                            break;
                        case action.add:
                        case action.replace:
                            string selectedFile = entry.fileStatus.Value;
                            /*if (kvp.Value == action.replace)
                            {
                                selectedFile = listReplace[kvp.Key];
                                worker.ReportProgress(0, count + "/" + listComplete.Count + ": Replacing " + Path.GetFileName(selectedFile));
                            }
                            else
                            {
                                selectedFile = listAdd[kvp.Key].filePath;
                                worker.ReportProgress(0, count + "/" + listComplete.Count + ": Adding " + Path.GetFileName(selectedFile));
                            }*/
                            output.Seek((long)outPointerBlockSize, 0);
                            //compressing the replacing file
                            MemoryStream encStream = new MemoryStream();
                            ushort[] blockSizeArray;
                            FileStream streamFile = File.OpenRead(selectedFile);

                            if ((Path.GetExtension(selectedFile) == ".bik" ||
                                 Path.GetExtension(selectedFile) == ".afc") &&
                                entry.blockSizeIndex == -1)
                            {
                                streamFile.CopyTo(encStream);
                                outBlockIndex = -1;
                            }
                            else
                            {
                                CompressFile(streamFile, out blockSizeArray, encStream);
                                for (int i = 0; i < blockSizeArray.Length; i++)
                                {
                                    output.WriteValueU16(blockSizeArray[i]);
                                }
                                outPointerBlockSize += (blockSizeArray.Length * 2);
                                blockIndexCounter += blockSizeArray.Length;
                            }
                            output.Seek((long)outPointerData, 0);
                            encStream.WriteTo(output);
                            outPointerData += (int)encStream.Length;

                            fileSize = (int)streamFile.Length;
                            break;
                    }// end switch

                    output.Seek((long)outPointerEntry, 0);
                    output.WriteValueU32(entry.nameHash.A.Swap());
                    output.WriteValueU32(entry.nameHash.B.Swap());
                    output.WriteValueU32(entry.nameHash.C.Swap());
                    output.WriteValueU32(entry.nameHash.D.Swap());
                    output.WriteValueS32(outBlockIndex);
                    output.WriteValueS32(fileSize);
                    output.WriteValueU8(0);
                    output.WriteValueS32(outDataOffset);
                    output.WriteValueU8(0);
                    outPointerEntry += 0x1E;
                    if (outPointerEntry > outInitialBlockOffset)
                    {
                        throw new Exception("Entry index offset values out of range");
                    }
                }// end of foreach

                //writing the file list entry, blocksizes & data
                outDataOffset = outPointerData;
                outBlockIndex = blockIndexCounter;
                {
                    MemoryStream streamRead = new MemoryStream(ASCIIEncoding.Default.GetBytes(dlcFileList));
                    ushort[] blockSizeArray;
                    MemoryStream encStream = new MemoryStream();
                    CompressFile(streamRead, out blockSizeArray, encStream);

                    output.Seek((long)outPointerBlockSize, 0);
                    for (int i = 0; i < blockSizeArray.Length; i++)
                    {
                        output.WriteValueU16(blockSizeArray[i]);
                    }
                    outPointerBlockSize += (blockSizeArray.Length * 2);
                    blockIndexCounter += blockSizeArray.Length;

                    output.Seek((long)outPointerData, 0);
                    encStream.WriteTo(output);
                    outPointerData += (int)encStream.Length;

                    fileSize = (int)streamRead.Length;

                    output.Seek((long)outPointerEntryFileList, 0);
                    output.WriteValueU32(DLCHandler.fileListHash.A.Swap());
                    output.WriteValueU32(DLCHandler.fileListHash.B.Swap());
                    output.WriteValueU32(DLCHandler.fileListHash.C.Swap());
                    output.WriteValueU32(DLCHandler.fileListHash.D.Swap());
                    output.WriteValueS32(outBlockIndex);
                    output.WriteValueS32((int)fileSize);
                    output.WriteValueU8(0x00);
                    output.WriteValueS32(outDataOffset);
                    output.WriteValueU8(0x00);
                    outPointerEntry = (int)output.Position;
                }

            }// end of using...

            File.Delete(inputPath);
            File.Move(newSfarFile,inputPath);

            
        }
    }

#endregion
}
